package midas.mw.crt;

import midas.mw.cfg.CfgError;
import midas.mw.cfg.CfgLogging;
import midas.mw.cfg.interfaces.RegisteredComponent;
import midas.mw.cfg.log.Logger;
import midas.mw.cfg.mwmonitor.CFGIllegalOperationException;
import midas.mw.cfg.mwmonitor.MWMonitor;

import midas.mw.interfaces.Component;
import midas.mw.interfaces.Mw;

import midas.mw.crt.interfaces.CrtCallback;
import midas.mw.crt.interfaces.Crt;
import midas.mw.crt.common.IPMessage;
import midas.mw.crt.Constants;
import midas.mw.crt.jxta.JxtaModule;
import midas.mw.crt.presence.NokiaPresenceThread;
//import midas.mw.crt.network.NetworkConfigurator;

import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Hashtable;

import org.apache.log4j.PropertyConfigurator;

/**
 * This class implements the manager of the CRT. It manages the different
 * subcomponents and act as a proxy between the Middleware core and these
 * subcomponents
 */
public class CrtImpl implements Crt, Component
{
	/**
	 * This hashtable stores information about the components which
	 * implement CrtCallbck and has been registered as CRT users
	 */
	private Hashtable crtUsers 							= null;
	
	/**
	 * Instance of the Middleware running in the node
	 */
	private Mw middlewareInterface						= null;	
	
	/**
	 * Instance of the Network Manager running in the node
	 */
	private NetworkManager networkManager 				= null;
	
	/**
	 * INstance of the Network Configurator running in the node
	 */
	//private NetworkConfigurator networkConfigurator 	= null;
	
	/**
	 * Instance of the Message Dispatcher running in the node
	 */
	private MessageDispatcher messageDispatcher 		= null;
	
	/**
	 * Instance of the Message Receiver running in the node
	 */
	private MessageReceiver messageReceiver 		= null;
	
	/**
	 * Instance of the Node Identifier Solver running in the node
	 */
	private NodeIdentifierSolver nodeIdentifierSolver 	= null;
	
	/**
	 * Instance of the Packet Forwarder running in the node
	 */
	private PacketForwarder packetForwarder 			= null;
	
	/**
	 * Instance of the JXTA module running in the node
	 */
    private JxtaModule jxtaModule 			= null;
    
    /**
	 * Instance of the Context Based Router running in the node
	 */
    private CbrImpl cbr									= null;
    
    
    /**
     * Object to store and retrieve all the necessary parameters to make 
     * the CRT working
     */
    private Constants constants							= null;
    
    /**
     * Thread to manage the presence of Nokia nodes
     */
    private NokiaPresenceThread nokiaPresenceThread = null;
    
    /**
     * Utility to register the CRT as a Middleware component. It will allow to notify the 
     * different status of the component
     */
    private RegisteredComponent crtRegisteredComponent = null;
    
	/**
	 * String used to provide locks
	 */
    public String responseLock 		= new String("responseLock");
    
	/**
	 * Constructor - initiate local variables
	 */
	public CrtImpl()
	{
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl()");
		
		PropertyConfigurator.configure("conf/log4j.properties");
					
		crtUsers 					= new Hashtable();
		networkManager 				= new NetworkManager(this);
		//networkConfigurator			= new NetworkConfigurator(this);                                                                                                                                                                                                                                                           
		messageDispatcher			= new MessageDispatcher(this);
		messageReceiver				= new MessageReceiver();
		nodeIdentifierSolver		= new NodeIdentifierSolver(this);
		packetForwarder				= new PacketForwarder(this);
		cbr							= new CbrImpl();
		nokiaPresenceThread		 	= new NokiaPresenceThread(this);
		//jxtaModule					= new JxtaModule(this);
		constants					= new Constants();
		
		try {
			crtRegisteredComponent = MWMonitor.cfgRegisterComponent(MWMonitor.COMPONENT_CRT);
		} catch (CFGIllegalOperationException e){e.printStackTrace();}
	}
	
	/**
     *  {@inheritDoc}
     */
	public String getOwnNodeId()
	{			
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getOwnNodeId()");
		
		return networkManager.getOwnNodeId();
	}
	
	/**
     *  {@inheritDoc}
     */
	public Properties getNeighbourhoodInfo(int minNumHops, int maxNumHops)
	{	
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getNeighbourhoodInfo(" + minNumHops + ", " + maxNumHops + ")");
		
		Properties neighbourhoodInfo = null;
		if (!jxtaModule.isStarted())
		{
			CfgError.errorReporting(CfgError.ERR_INFO, Logger.COMPONENT_CRT, CfgError.SEV_INFORMAL,
					"CrtImpl.getNeighbourhoodInfo(). JxtaModule has not been started", null);
			neighbourhoodInfo = new Properties();
		}
		else
		{
			try { 
				neighbourhoodInfo = networkManager.getNeighbourhoodInfo(minNumHops, maxNumHops);
			} catch (CrtException crte) {
			}
		}
		return neighbourhoodInfo;
	}

	/**
     *  {@inheritDoc}
     */
	public void addCrtListener(String userId, CrtCallback crtUser)
	{
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.addCrtListener(" + userId + ", crtUser)");
		
		if (!crtUsers.containsKey(userId))
			crtUsers.put(userId, crtUser);
		/*
		System.out.println("IMPRIMO LA TABLA DE APLICACIONES");
		Iterator it = crtUsers.entrySet().iterator();
		while (it.hasNext())
		{
			Map.Entry entry = (Map.Entry)it.next();
			System.out.println((String)entry.getKey() + "----" + ((CrtCallback)entry.getValue()).toString());														
		}	
		*/	
	}
	
	/**
     *  {@inheritDoc}
     */
	public void removeCrtListener(String userId)
	{
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.removeCrtListener(" + userId + ")");
		
		if (crtUsers.containsKey(userId))
			crtUsers.remove(userId);
		
		/*System.out.println("IMPRIMO LA TABLA DE APLICACIONES");
		Iterator it = crtUsers.entrySet().iterator();
		while (it.hasNext())
		{
			Map.Entry entry = (Map.Entry)it.next();
			System.out.println((String)entry.getKey() + "----" + ((CrtCallback)entry.getValue()).toString());														
		}*/
	}
	
	/**
	 * Get reference to the specified CRT user
	 * @param crtUser The name of the CRT user (e.g. 'MW_Core')
	 * @return reference to the specified CRT user; null if an error occurs
	 */
	public CrtCallback getCRTUser(String crtUser) 
	{
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getCRTUser(" + crtUser + ")");
		
		CrtCallback crtCallback = (CrtCallback) crtUsers.get(crtUser);
		if (crtCallback == null) {
    		CfgError.errorReporting(CfgError.ERR_INFO, Logger.COMPONENT_CRT, CfgError.SEV_INFORMAL, 
					"CrtImpl.getCRTUser(" + crtUser + "). CRT user '"+ crtUser + "' not available", null);
		}
		return crtCallback;
	}
	
	/**
     *  {@inheritDoc}
     */
	public void subscribePresence(String userId, String nodeId)
	{
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.subscribePresence(" + userId + ", " + nodeId + ")");
		
		//Implementation of the mechanisms for the presence subscription		
		/*if (crtListeners.containsKey(listenerId))
		{
			CrtCallbackInterface listener = (CrtCallbackInterface) crtListeners.get(listenerId);
			networkManager.registerPresence(listener, nodeId);
		}*/
		CrtCallback crtUser = (CrtCallback) middlewareInterface.getRunningComponent(userId);
		//else
		//	listener = (CrtCallback) middlewareInterface.getRunningApplications().get(listenerId);
		
		if (crtUser != null)
			networkManager.registerPresence(crtUser, nodeId);	
	}
	
	/**
     *  {@inheritDoc}
     */
	public void unsubscribePresence(String userId, String nodeId)
	{
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.unsubscribePresence(" + userId + ", " + nodeId + ")");
		
		//Implementation of the mechanisms for the presence unsubscription
		/*if (crtListeners.containsKey(listenerId))
		{
			CrtCallbackInterface listener = (CrtCallbackInterface) crtListeners.get(listenerId);
			networkManager.unregisterPresence(listener, nodeId);
		}*/
		CrtCallback crtUser = (CrtCallback) middlewareInterface.getRunningComponent(userId);
		//else
		//	listener = (CrtCallback) middlewareInterface.getRunningApplications().get(listenerId);
		
		if (crtUser != null)
			networkManager.unregisterPresence(crtUser, nodeId);
	}
	
	/**
     *  {@inheritDoc}
     */
	public long sendMessage(IPMessage ipMessage)
	{	
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "sendMessage(" + ipMessage.msgToString() + ")");
		//try {
		long messageId = -1;
		
		if (!jxtaModule.isStarted())
			CfgError.errorReporting(CfgError.ERR_INFO, Logger.COMPONENT_CRT, CfgError.SEV_INFORMAL,
					"CrtImpl.sendMessage(IPMessage). JxtaModule has not been started", null);					
		else
			messageId = messageDispatcher.sendMessage(ipMessage);
		/*	packetForwarder.sendMessage(ipMessage);
		} catch (CrtException crte) {			
		}*/
		return messageId;
	}
	
	/**
     *  {@inheritDoc}
     */
	public void sendBroadcastMessage(IPMessage ipMessage) 
	{
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.sendBroadcastMessage(" + ipMessage.msgToString() + ")");
		
		if (!jxtaModule.isStarted())		
			CfgError.errorReporting(CfgError.ERR_INFO, Logger.COMPONENT_CRT, CfgError.SEV_INFORMAL,
					"CrtImpl.sendBroadcastMessage(IPMessage). JxtaModule has not been started", null);
		else
		{
			try {
				jxtaModule.propagateMessage(ipMessage);
			} catch (CrtException crte){}
		}
	}
	
	/**
     *  {@inheritDoc}
     */
	public void sendOneHopMessage(IPMessage ipMessage) 
	{
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, 
				"CrtImpl.sendOneHopMessage(" + ipMessage.msgToString() + ")");
		
		if (!jxtaModule.isStarted())		
			CfgError.errorReporting(CfgError.ERR_INFO, Logger.COMPONENT_CRT, CfgError.SEV_INFORMAL,
					"CrtImpl.sendOneHopMessage(IPMessage). JxtaModule has not been started", null);
		else
		{
			ipMessage.setConfirmation(false);
			Iterator it = getNeighbourhoodInfo(1, 1).keySet().iterator();
			while (it.hasNext())
			{
				ipMessage.setDestinationNodeId((String)it.next());
				sendMessage(ipMessage);
			}
		}
	}
	
	/**
     *  {@inheritDoc}
     */
	public void deleteMessage(long messageId) 
	{	
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.deleteMessage(" + messageId + ")");
		
		Long mId = new Long(messageId);
		IPMessage ipMessage = (IPMessage)messageDispatcher.getMessageList().get(mId);
		if (ipMessage != null)
		{
			messageDispatcher.getTimeQueue().deleteMessage(ipMessage);
		    messageDispatcher.getNodeList().deleteMessage(ipMessage);
			messageDispatcher.getMessageList().remove(mId);
			SearchThread st = (SearchThread) messageDispatcher.getSearchingThreads().get(ipMessage.getDestinationNodeId());
			if (st != null)
			{
				st.stopThread();
				//messageDispatcher.getSearchingThreads().remove(mId);
			}			
			messageDispatcher.stopWaiting(ipMessage.getDestinationNodeId());			
		}
	}
	
	/**
	 * Starts the CRT
	 * @param middlewareInterface Instance of the Middleware running in the node
	 * @return 0 if the CRT starts properly. -1 otherwise
	 */
	public int start(Mw middlewareInterface)
	{			
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.start(MiddlewareInterface)");
		
		this.middlewareInterface = middlewareInterface;
	
		try {
			
			MWMonitor.cfgSetComponentStatus(crtRegisteredComponent, MWMonitor.STATUS_STARTING);
			
			constants.configure();						
						
			//Starting the configured interfaces
			//I have to remake how to start the different network interfaces.
			//For the momenent I'm not going to do anything. The node will use the available
			//network interfaces
			//networkManager.start();
						
			try {
				Thread.sleep(3000);
			} catch (InterruptedException ie){
				ie.printStackTrace();
			}
			
			//Depending on the configuration a different class could be used
			//String usedClass 	= "midas.mw.crt.jxta.JxtaModule";
			//if (constants.isCentralNode())
			//	usedClass = "midas.mw.crt.jxta.JxtaModule";	
			
			
			jxtaModule = new JxtaModule(this);
								
			
			if (networkManager.isInAdHocMode())
			{
				Thread networkManagerThread = new Thread(networkManager);
				networkManagerThread.start();
			}			
			
			cbr.start(middlewareInterface);
		    //messageDispatcher.start();
						
			jxtaModule.configurePlatform();	
			jxtaModule.startPlatform();
			
			jxtaModule.setStarted(true);
			Thread jxtaThread = new Thread(jxtaModule);
			jxtaThread.start();
			
			nokiaPresenceThread.start();
		    	
		} catch (CrtException crte) {	
			MWMonitor.cfgSetComponentStatus(crtRegisteredComponent, MWMonitor.STATUS_ERROR);
			jxtaModule.setStarted(false);
			return -1;
		}	
		
		MWMonitor.cfgSetComponentStatus(crtRegisteredComponent, MWMonitor.STATUS_STARTED);

	    return 0;
	}
	
	/**
	 * When the CRT starts, it waits for 90s to get a connection with the CN. If the connection
	 * is got before the time ends, this function is called
	 */
	public void stopWaiting()
	{	
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.stopWaiting()");	
		//md.getWaitingThreads().remove(messageId);
		synchronized(responseLock) {
			responseLock.notify();
		}
	}
	
	/**
	 * Stops the CRT
	 * @return 0 if the CRT stops properly. -1 otherwise
	 */
	public int stop()	
	{	
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.stop()");
		
		MWMonitor.cfgSetComponentStatus(crtRegisteredComponent, MWMonitor.STATUS_DYING);		
		
		if (jxtaModule != null)
		{
			try {
				//if (jxtaModule.getJxtaStatus())
				jxtaModule.stopJxtaModule();
				jxtaModule = null;
			} catch (CrtException crte) {
				MWMonitor.cfgSetComponentStatus(crtRegisteredComponent, MWMonitor.STATUS_ERROR);
				return -1;
			}
		}
		
		crtUsers.clear();
		nodeIdentifierSolver.stopNodeIdentifierSolver();
		networkManager.stopNetworkManager();
		messageDispatcher.stopMessageDispatcher();
		cbr.stop();
		
		MWMonitor.cfgSetComponentStatus(crtRegisteredComponent, MWMonitor.STATUS_DEAD);
		
		return 0;
	}
	
	/**
	 * Provides the instance of the Middleware running in the node
	 * 
	 * @return The instance of the Middleware running in the node
	 */
	public Mw getMiddlewareInterface()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getMiddlewareInterface()");
		
		return middlewareInterface;
	}
	
	/**
	 * Returns the instance of the Network Manager running in the node
	 * 
	 * @return The instance of the Network Manager running in the node
	 */
	public NetworkManager getNetworkManager()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getNetworkManager()");
		
		return networkManager;
	}
	
	/**
	 * Returns the instance of the Message Dispatcher running in the node
	 * 
	 * @return The instance of the Message Dispatcher running in the node
	 */
	public MessageDispatcher getMessageDispatcher()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getMessageDispatcher()");
		
		return messageDispatcher;
	}
	
	/**
	 * Returns the instance of the Node Identifier Solver running in the node
	 * 
	 * @return The instance of the Node Identifier Solver running in the node
	 */
	public NodeIdentifierSolver getNodeIdentifierSolver()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getNodeIdentifierSolver()");
		
		return nodeIdentifierSolver;
	}
	
	/**
	 * Returns the instance of the Packet Forwarder running in the node
	 * 
	 * @return The instance of the Packet Forwarder running in the node
	 */
	public PacketForwarder getPacketForwarder()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getPacketForwarder()");
		
		return packetForwarder;
	}
	
	/**
	 * Returns the instance of the JXTA module running in the node
	 * 
	 * @return The instance of the JXTA module running in the node
	 */
	public JxtaModule getJxtaPlatform()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getJxtaPlatform()");
		
		return jxtaModule;
	}
	
	/**
	 * Returns the instance of the Message Receiver running in the node
	 * 
	 * @return The instance of the Message Receiver running in the node
	 */
	public MessageReceiver getMessageReceiver()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getMessageReceiver()");
		
		return messageReceiver;
	}
	
	/**
	 * Returns the instance of the NokiaNodesPresenceThread running in the node
	 * @return The instance of the NokiaNodesPresenceThread running in the node
	 */
	public NokiaPresenceThread getNokiaPresenceThread()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getNokiaNodesPresenceThread()");
		
		return nokiaPresenceThread;
	}
	
	/**
	 * Returns the object storing the necessary parameters of the CRT
	 * 
	 * @return The object storing the necessary parameters of the CRT
	 */
	public Constants getConstants()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getConstants()");
		
		return constants;
	}
	
	/**
	 * Returns the instance of the Network Configurator running in the node
	 * 
	 * @return The instance of the Nettwork Configurator running in the node
	 */
	/*public NetworkConfigurator getNetworkConfigurator()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.getNetworkConfigurator()");

		return networkConfigurator;
	}*/
	
	/**
	 * Allows to get the CRT as a registered component of the Middleware. It will be used by other classes
	 * in the CRT to change CRT status (or other parameters associated to the Common Functions)
	 * @return The CRT as a registered component of the Middleware
	 */
	public RegisteredComponent getCrtRegisteredComponent()
	{
		return crtRegisteredComponent;
	}

	/**
     *  {@inheritDoc}
     */
	public void registerCBRCallback(CrtCallback crtCallback) 
	{
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.registerCBRCallback(CrtCallback)");
		
		cbr.registerCBRCallback(crtCallback);
	}

	/**
     *  {@inheritDoc}
     */
	public void sendMessage(String destination, byte[] payload, int flags) 
	{
		CfgLogging.logEvent(CfgLogging.LOG_API, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CrtImpl.sendMessage(" + destination + ", byte[] " + flags + ")");
		
		cbr.sendMessage(destination, payload, flags);	
	}
}
